package lode

import (
	"fmt"
	"github.com/Unixeno/islands/conf"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"sync/atomic"
	"syscall"
	"time"
)

// 导入原始镜像，镜像应为整个rootfs的打包
// 返回信息同ImportDockerImage()
func ImportRawImage(path string, name string) (string, error) {
	if imageIsExist(name) {
		logger.Error("The image is exist")
		return "", fmt.Errorf("The image is exist")
	}
	return rawImport(path, name)
}

func rawImport(path string, name string) (string, error) {
	// if file not exist
	if !exists(path) {
		logger.Errorf("can't find the file : %s", path)
		return "", fmt.Errorf("can't find the file : %s", path)
	}

	logger.WithField("image", path).Info("start importing raw image")

	layerId, err := lodeDB.layers.storeTar(path, "tar")
	if err != nil {
		logger.WithError(err).Error("failed to import raw image")
		return "",fmt.Errorf("failed to import image with with error : %v\n", err)
	}

	r := BaseImage{
		Created:      time.Now(),
		Architecture: runtime.GOARCH,
		Os:           "linux",
		Config:       BaseImageConfig{},
		Rootfs:       []string{layerId},
	}

	logger.Debug("store the image to repository...")

	id, err := lodeDB.images.store(r, name)
	if err != nil {
		logger.WithError(err).Error("failed to import raw image")
		return "", err
	} else {
		logger.WithField("ID", id).Debug("import raw image success")
		return id, nil
	}
}

// 列出所有镜像
func List() []Image {
	ret := make([]Image, 0, 0)
	lodeDB.images.syncDB.Range(func(key, image interface{}) bool {
		ret = append(ret, image.(*imageInfoEntry))
		return true
	})
	return ret
}

// 打开一个本地镜像
//
// 若打开成功，则返回一个镜像实例
func OpenImage(id string) (Image, error) {
	if image, ok := lodeDB.images.syncDB.Load(id); ok {
		return image.(*imageInfoEntry), nil
	} else {
		err := fmt.Errorf("the specified image doesn't exist")
		logger.Error("the specified image doesn't exist")
		return nil, err
	}
}

//用来挂载镜像
//
//若挂载成功，则返回rootfs
func (image imageInfoEntry) Mount() (string, error) {
	rootPath := conf.GetString("RootPath")
	var opts string

	path := filepath.Join(rootPath, "mnt", image.Id)
	workDir := filepath.Join(path, "work")
	mergeDir := filepath.Join(path, "merge")

	if err := os.MkdirAll(workDir, 0700); err != nil {
		return "", err
	}
	if err := os.MkdirAll(mergeDir, 0700); err != nil {
		return "", err
	}

	//如果镜像只有一层的话就直接返回这一层的目录
	//TODO 只能以读写的方式进行挂载
	if len(image.Info.Rootfs) == 1 {
		source := lodeDB.layers.getPath(image.Info.Rootfs[0])
		if err := syscall.Mount(source, mergeDir, "none", syscall.MS_BIND|syscall.MS_RDONLY, ""); err != nil {
			logger.Error("failed to mount ", image.Id)
			return "", err
		}
	} else {
		//如果镜像有多层的话就直接返回挂载好的镜像地址
		layers := make([]string, len(image.Info.Rootfs))
		for index := 0; index < len(image.Info.Rootfs); index++ {
			layers[len(image.Info.Rootfs)-index-1] = lodeDB.layers.getPath(image.Info.Rootfs[index])
		}
		lowDir := strings.Join(layers, ":")
		opts = fmt.Sprintf("lowerdir=%s,workdir=%s", lowDir, workDir)

		if err := syscall.Mount("overlay", mergeDir, "overlay", 0, opts); err != nil {
			logger.Error("failed to mount ", image.Id)
			return "", err
		}
	}
	logger.Debug(image.Id, "has been mounted on ", mergeDir)
	return mergeDir, nil
}

//用来卸载镜像
func (image imageInfoEntry) Unmount() error {
	rootPath := conf.GetString("RootPath")
	path := filepath.Join(rootPath, "mnt", image.Id, "merge")
	if err := syscall.Unmount(path, 0); err != nil {
		logger.WithField("id", image.Id).WithError(err).Error("unmount failed")
		return err
	}
	if err := os.RemoveAll(filepath.Join(rootPath, "mnt", image.Id)); err != nil {
		logger.WithField("id", image.Id).WithError(err).Error("unmount failed")
		return err
	}
	logger.Debug(image.Id, " has been unmounted")
	return nil
}

func (image imageInfoEntry) Rename(name string) {
	if value, ok := lodeDB.images.syncDB.Load(image.Id); ok {
		value.(*imageInfoEntry).Name = name
		lodeDB.images.save()
		return
	}
	logger.Error("image doesn't exist")
}

//返回镜像的挂载点
func (image imageInfoEntry) MountPoint() string {
	rootPath := conf.GetString("RootPath")
	return filepath.Join(rootPath, "mnt", image.Id, "merge")
}

//被容器使用之前要先进行注册
func (image imageInfoEntry) Register() {
	logger.WithField("id", image.Id).Debug("unregister image")
	if nil != lodeDB.images.register(image.Id) {
		panic("register failed, the image doesn't exist")
	}
}

//当容器被销毁要取消对其注册
func (image imageInfoEntry) UnRegister() {
	if nil != lodeDB.images.unRegister(image.Id) {
		panic("unregister failed, the image doesn't exist")
	}
}

// 返回镜像的基础信息
func (image imageInfoEntry) Config() BaseImageConfig {
	return image.Info.Config
}

func (image imageInfoEntry) GetName() string {
	return image.Name
}

func (image imageInfoEntry) GetID() string {
	return image.Id
}

func (image imageInfoEntry) GetCounter() int32 {
	return atomic.LoadInt32(&image.Counter)
}

func (image imageInfoEntry) GetCreateTime() time.Time {
	return image.Info.Created
}

func (image imageInfoEntry) Export(dstTar string) error {
	imagePath, err := image.Mount()
	defer image.Unmount()
	if err != nil {
		logger.WithError(err).Error("failed to mount image")
		return err
	}
	if err := tarDir(imagePath, dstTar); err != nil {
		logger.WithError(err).Error("failed to tar the image")
		return err
	}
	logger.Debug(image.Id, " has been exported as ", dstTar)
	return nil
}
